// ==============================================================================
// MessageSenderGateway provides functionality for sending messages to MSMQ.
// 
// MessageSenderGateway.cs
// @author Abel Perez <java.aperez@gmail.com>
// ==============================================================================

using System;
using System.Messaging;
using System.Xml;

namespace Justa.Messaging
{
    /// <summary>
    /// MessageSenderGateway provides functionality for sending and receiving 
    /// messages synchronously to MSMQ.  
    ///
    /// <remarks>
    /// This gateway is somewhat of a hybrid 
    /// gateway since it can both send and receive messages.  The simplest 
    /// operation provided is Send.  Through the Send operations this gateway 
    /// can send messages to a specified destination in MSMQ.  
    /// 
    /// In addition to being able to send messages, this gateway essentially 
    /// implements the half-sunc/half-async messaging pattern.  For example, 
    /// when the Request operation is invoked a temporary response queue will 
    /// be created and set as the response queue of the outgoing message.  A 
    /// message consumer listening for messages on the target destination 
    /// queue can consume an incomming message, process the message, and send 
    /// the results to the response queue defined in the incomming message.  
    /// The message id in the incomming message must be set as the correlation 
    /// id on the newly outgoing response message.  This gateway will 
    /// correlate incomming response messages based on the incomming correlation 
    /// id.  The correlation functionality allows this gateway to preserve a 
    /// stateless state and therefore can be considered thread safe. The 
    /// default message formatter used by this gateway is an XML message 
    /// formatter, the most common use for this gateway is to send and recieve 
    /// xml based messages.
    /// </remarks>
    /// 
    /// <example>
    /// For example, the following code snippet sends a ping message to the 
    /// specified destination and synchronously blocks until a response message 
    /// is received or the specified timeout is exceeded.
    /// 
    /// <code>
    ///     string destination = "Unified.AutoLoan.CarsDirect.Ping";
    ///     string name = "CarsDirectGateway";
    ///     string requestMessage = "<ping>Hello World</ping>";
    ///     
    ///     MessageSenderGateway gateway = new MessageSenderGateway(destination, name);
    ///     string responseMessage = gateway.Request(requestMessage, 15);
    /// </code>
    /// </example>
    /// </summary>
    /// 
    public class MessageSenderGateway : GenericMessageGateway
    {
        /// <summary>
        /// 
        /// </summary>
        private MessageQueue destination;

        /// <summary>
        /// 
        /// </summary>
        public static string DEFAULT_RESPONSE_QUEUE = "default.gateway.response.queue";


        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="destination"></param>
        public MessageSenderGateway(String destination)
        {
            this.destination = GetMessageQueue(destination);

            if (destination == null)
            {
                throw new ApplicationException("Failed to initialize MessageSenderGateway.");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="message"></param>
        public void Send(XmlDocument message)
        {
            OneWay(message.OuterXml);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="message"></param>
        public void Send(string message)
        {
            OneWay(message);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="destination"></param>
        /// <param name="message"></param>
        /// 
        public void Send(string destination, string message)
        {
            OneWay(GetMessageQueue(destination), message);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="destination"></param>
        /// <param name="message"></param>
        /// 
        public void Send(string destination, XmlDocument message)
        {
            OneWay(GetMessageQueue(destination), message);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public string Request(string message)
        {
            return this.Request(message, 0);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="message"></param>
        /// <returns></returns>
        /// 
        public string Request(XmlDocument message)
        {
            return this.Request(message.OuterXml, 0);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="message"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        /// 
        public string Request(XmlDocument message, int timeout)
        {
            return this.Request(message.OuterXml, timeout);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="destination"></param>
        /// <param name="message"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public string Request(string destination, XmlDocument message, int timeout)
        {
            return this.Request(destination, message.OuterXml, timeout);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="message"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        /// 
        public string Request(string message, int timeout)
        {
            return this.Request(DEFAULT_RESPONSE_QUEUE, message, timeout);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="responseDestination"></param>
        /// <param name="msg"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        /// 
        public string Request(string responseDestination, string msg, int timeout)
        {
            MessageQueue responseQueue = GetMessageQueue(responseDestination);

            Message message = new Message(msg);
            message.ResponseQueue = responseQueue;

            OneWay(message);
            string correlationId = message.Id;

            Message response = null;
            if (timeout <= 0)
            {
                response = responseQueue.ReceiveByCorrelationId(correlationId);
            }
            else
            {
                response = responseQueue.ReceiveByCorrelationId(correlationId, new System.TimeSpan(0, 0, timeout));
            }

            response.Formatter = new System.Messaging.XmlMessageFormatter(new String[] { "System.String" });
            return response.Body.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="targetDestination"></param>
        /// <param name="responseDestination"></param>
        /// <param name="msg"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        /// 
        public string Request(string targetDestination, string responseDestination, string msg, int timeout)
        {
            MessageQueue responseQueue = GetMessageQueue(responseDestination);

            Message message = new Message(msg);
            message.ResponseQueue = responseQueue;

            OneWay(GetMessageQueue(targetDestination), message);
            string correlationId = message.Id;

            Message response = null;
            if (timeout <= 0)
            {
                response = responseQueue.ReceiveByCorrelationId(correlationId);
            }
            else
            {
                response = responseQueue.ReceiveByCorrelationId(correlationId, new System.TimeSpan(0, 0, timeout));
            }

            response.Formatter = new System.Messaging.XmlMessageFormatter(new String[] { "System.String" });
            return response.Body.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="message"></param>
        private void OneWay(Object message)
        {
            try
            {
                destination.Send(message);
            }
            catch (MessageQueueException exception)
            {
                throw new ApplicationException("Failed to send message.", exception);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="messageQueue"></param>
        /// <param name="message"></param>
        /// 
        private void OneWay(MessageQueue messageQueue, Object message)
        {
            try
            {
                messageQueue.Send(message);
            }
            catch (MessageQueueException exception)
            {
                throw new ApplicationException("Failed to send message.", exception);
            }
        }
    }
}